﻿// This file is part of Code4Public (http://code4public.codeplex.com)
// Copyright 2011 Sina Iravanian - <sina@sinairv.com>
//
// This source file(s) may be redistributed, altered and customized
// by any means PROVIDING the authors name and all copyright
// notices remain intact.
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED. USE IT AT YOUR OWN RISK. THE AUTHOR ACCEPTS NO
// LIABILITY FOR ANY DATA DAMAGE/LOSS THAT THIS PRODUCT MAY CAUSE.
// ------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Code4Public.Languages;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Code4Public.Parsing;
using Code4Public.Settings;

namespace Code4Public.GUI
{
    public class SyntaxHighlighter
    {
        #region Private Fields
        /// <summary>
        /// Reference to the text-box instance, for which 
        /// the syntax highlighting is going to occur.
        /// </summary>
        private RichTextBox TextBox;

        /// <summary>
        /// The reference to the logic core.
        /// </summary>
        private Core core;

        /// <summary>
        /// Determines whether the program is busy creating rtf for the previous
        /// modification of the text-box. It is necessary to avoid blinks when the 
        /// user is typing fast.
        /// </summary>
        private bool isDuringHighlight = false;
        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="SyntaxHighlighter"/> class.
        /// Setting references to Core, and TextBox, and then creating proper event-handler
        /// for the text-box instance.
        /// </summary>
        /// <param name="richTextBox">The rich text box to be highlighted.</param>
        /// <param name="core">The logic core of the program.</param>
        public SyntaxHighlighter(RichTextBox richTextBox, Core core)
        {
            Debug.Assert(richTextBox != null);
            Debug.Assert(core != null);

            this.TextBox = richTextBox;
            this.core = core;

            this.DisableHighlighting = false;

            TextBox.TextChanged += new EventHandler(TextBox_TextChanged);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether highlighting should be disabled or not.
        /// If true, the user input will remain intact. If false, the rich content will be
        /// modified to match the syntax of the currently selected language.
        /// </summary>
        public bool DisableHighlighting { get; set; }
        
        #endregion

        #region RTF Stuff
        private string RTFHeader()
        {
            return @"{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Courier New;}}";
        }

        private string RTFColorTable()
        {
            return core.GetRtfColorTable();
        }

        private string RTFContent(string content)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine(RTFHeader());
            sb.AppendLine(RTFColorTable());
            sb.Append(@"\viewkind4\uc1\pard\f0\fs17 ");

            sb.Append(content);

            sb.Append("}");
            return sb.ToString();
        }

        #endregion

        #region Highlighting Stuff

        /// <summary>
        /// Handles the TextChanged event of the TextBox control. 
        /// Actually rehighlighting the content.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void TextBox_TextChanged(object sender, EventArgs e)
        {
            ReHighlight();
        }

        /// <summary>
        /// Rehighlights the text-box content.
        /// </summary>
        public void ReHighlight()
        {
            if (!DisableHighlighting)
            {
                if (isDuringHighlight) return;
                
                TextBoxUtils.DisableAndDoAndEnable(this.TextBox, HighlighTextBase);
            }
        }

        /// <summary>
        /// The base method that highlights the text-box content.
        /// </summary>
        private void HighlighTextBase()
        {
            isDuringHighlight = true;

            try
            {
                LanguageInfo selectedLanguage = core.Languages.SelectedLanguage;

                // since the ReHighlight() method should have checked this.
                if (selectedLanguage == null)
                    Debug.Assert(false);

                // create tab replacement string once forever
                string tabReplacement = "\t";
                if (AppSettings.Settings.ReplaceTabs)
                {
                    StringBuilder sbTab = new StringBuilder();
                    for (int i = 0; i < AppSettings.Settings.TabSize; ++i)
                    {
                        sbTab.Append(" ");
                    }
                    tabReplacement = sbTab.ToString();
                }


                ParserHelper helper = new ParserHelper(selectedLanguage);
                StringBuilder sb = new StringBuilder();

                sb.AppendLine(RTFHeader());
                sb.AppendLine(RTFColorTable());
                sb.Append(@"\viewkind4\uc1\pard\f0\fs17 ");

                foreach (var exp in helper.GetExpressions(TextBox.Text))
                {
                    if (exp.Type == ExpressionType.Whitespace)
                    {
                        string wsContent = exp.Content;
                        // TODO: the cursor cannot go to the end of the strings, it remains where it was
                        //if (AppSettings.Settings.ReplaceTabs && wsContent.IndexOf("\t") >= 0)
                        //{
                        //    wsContent = wsContent.Replace("\t", tabReplacement);
                        //}
                        sb.Append(wsContent);
                    }
                    else if (exp.Type == ExpressionType.Newline)
                    {
                        sb.AppendLine(@"\par");
                    }
                    else
                    {
                        string content = exp.Content.Replace("\\", "\\\\").Replace("{", @"\{").Replace("}", @"\}");

                        ColorProfile.GroupProperties props;
                        if (core.ColorProfiles.IsAnyProfileSelected &&
                            core.ColorProfiles.SelectedProfile.TryGetProperties(
                                Core.MapExpressionToColorProfileGroupName(exp), out props))
                        {
                            string opening = "", cloing = "";

                            if (props.Bold)
                            {
                                opening += @"\b";
                                cloing += @"\b0";
                            }

                            if (props.Italic)
                            {
                                opening += @"\i";
                                cloing += @"\i0";
                            }

                            sb.AppendFormat(@"\cf{0}{2} {1}\cf0{3} ", core.GetColorIndex(exp.Type, exp.Group),
                                content, opening, cloing);
                        }
                        else
                        {
                            sb.AppendFormat(@"\cf{0} {1}\cf0 ", core.GetColorIndex(exp.Type, exp.Group),
                                content);
                        }
                    }
                }

                sb.Append(@"\par }");

                TextBox.Rtf = sb.ToString();
            }
            finally
            {
                isDuringHighlight = false;
            }
        }

        #endregion
    }
}
